package jespeak.translation;

import java.util.logging.Level;
import jespeak.dict.DictRule;
import jespeak.dict.Dictionary;
import java.io.BufferedReader;
import java.io.DataInputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.StringTokenizer;
import java.util.logging.Logger;
import jespeak.dict.MatchedDictEntry;
import jespeak.rules.Group;
import jespeak.rules.LGroup;
import jespeak.rules.LLGroup;
import jespeak.rules.LSequence;
import jespeak.rules.MatchedRule;
import jespeak.rules.NLGroup;
import jespeak.rules.ReplaceRuleGroup;
import jespeak.rules.Rule;

public class Translator {

	private final String language;
	private final String rule_file;
	private final String list_file;
	private NLGroup group;
	private HashMap<String, LGroup> groups1;
	private HashMap<String, LLGroup> groups2;
	private ReplaceRuleGroup replace;
	private HashMap<Integer, LSequence> lgroups;
	private Dictionary dict;
	private int safety = -1;
	private final static Logger logger = Logger.getLogger(Translator.class.getName());

	private enum STATE {

		NL, L, LL, R, NONE
	}

	private boolean loadRuleFile() throws FileNotFoundException, IOException {
		FileInputStream fstream = new FileInputStream(rule_file);
		DataInputStream in = new DataInputStream(fstream);
		BufferedReader br = new BufferedReader(new InputStreamReader(in));
		String line;
		boolean parserule = false;
		STATE currentState = STATE.NONE;
		Group g = null;
		while ((line = br.readLine()) != null) {
			line = line.trim(); // trim leading and trailing whitespace
			if (line.isEmpty()) {
				continue;
			}
			if (line.startsWith("//")) {
				continue; // comment line
			}			// check if comments should be stripped
			int commentPos = line.indexOf("//");
			if (commentPos != -1) {
				line = line.substring(0, commentPos - 1);
			}
			if (line.startsWith(".L")) {
				if (g != null) {
					add(g, currentState);
					g = null;
				}
//				System.out.println(".L group");
				LSequence lg = new LSequence(line);
				lgroups.put(new Integer(lg.getNumber()), lg);
				parserule = false;
			} else if (line.startsWith(".replace")) {
				if (g != null) {
					add(g, currentState);
					g = null;
				}
//				System.out.println(".replace group");
				currentState = STATE.R;
				parserule = true;
			} else if (line.startsWith(".group")) {
				if (g != null) {
					add(g, currentState);
					g = null;
				}
//				System.out.println(line);
				StringTokenizer st = new StringTokenizer(line, " ");
				String str = st.nextToken();
				if (st.hasMoreTokens()) {
					str = st.nextToken();
					if (str.length() == 1) {
						g = new LGroup(str.charAt(0));
						currentState = STATE.L;
					} else { //if (str.length() == 2) {
						g = new LLGroup(str.charAt(0), str.charAt(1));
						currentState = STATE.LL;
					}
//					else {
					// should also handle rules like
					// .group 0xce   // Greek letters
					// 0xce is the first byte of the utf-8 code for these characters
//					}
				} else {
					g = new NLGroup();
					currentState = STATE.NL;
				}
				parserule = true;
			} else if (parserule == true) {
				// System.out.println(line);
				if (currentState == STATE.R) {
					StringTokenizer st = new StringTokenizer(line);
					replace.add(st.nextToken(), st.nextToken());
				} else {
					Rule rule = new Rule(line, dict);
					if (g.addRule(rule) == false) {
						return false;
					}
				}
			} else {
				return false;
			}
		}
		return true;
	}

	private boolean add(Group g, STATE s) {
		switch (s) {
			case NL:
				group = (NLGroup) g;
				break;
			case L:
				groups1.put(g.toString(), (LGroup) g);
				break;
			case LL:
				groups2.put(g.toString(), (LLGroup) g);
				break;
		}
		return true;
	}

	private void loadDictionary() throws FileNotFoundException, IOException {
		FileInputStream fstream = new FileInputStream(list_file);
		DataInputStream in = new DataInputStream(fstream);
		BufferedReader br = new BufferedReader(new InputStreamReader(in));
		String strLine;
		while ((strLine = br.readLine()) != null) {
			strLine = strLine.trim();
			if (strLine.isEmpty()) {
				continue;
			}
			if (strLine.startsWith("//")) {
				continue; // comment line
			}			// check if comments should be stripped
			int commentPos = strLine.indexOf("//");
			if (commentPos != -1 && strLine.startsWith("(") == false) {
				strLine = strLine.substring(0, commentPos - 1);
			}
			DictRule rule = new DictRule(strLine);
			dict.addDictRule(rule);
		}
		in.close();
	}

	public Translator(String lang, String rule_file, String list_file)
			  throws FileNotFoundException, IOException {
		this.language = lang;
		this.rule_file = rule_file;
		this.list_file = list_file;
		dict = new Dictionary("en");
		loadDictionary();
		group = new NLGroup();
		groups1 = new HashMap<String, LGroup>();
		groups2 = new HashMap<String, LLGroup>();
		replace = new ReplaceRuleGroup();
		lgroups = new HashMap<Integer, LSequence>();
		loadRuleFile();
	}

	public String getLanguage() {
		return language;
	}

	private MatchedDictEntry searchDict(String line, int pos) {
		// words in dict may be up to four words
		String currentLine = line.substring(pos);
		logger.log(Level.INFO, "searchDict called for {0}", currentLine);
		StringTokenizer st = new StringTokenizer(currentLine);
		int numWords = st.countTokens(), n = 4, i;
		if (numWords < 4)
			n = numWords;
		String dword = "";
		for (i = 1; i < n; ++i) {
			dword += st.nextToken() + " ";
		}
		dword += st.nextToken();
		// now dword has the maximum of words that should be searched
		String phoneme = "";
		boolean found = false;
		int c = 0;
		DictRule dr = null;
		while (c != -1 && found == false) {
			// search as is
			c = dword.lastIndexOf(" ");
			logger.log(Level.INFO, "last word ends at {0}", c);
			dr = dict.find(dword);
			logger.log(Level.INFO, "START searchDict called for {0}", dword);
			if (dr != null)
				found = true;
			else {
				// search tolower
				dr = dict.find(dword.toLowerCase());
				logger.log(Level.INFO, "START searchDict called for {0}", dword);
				if (dr != null)
					found = true;
				else if (c != -1)
					dword = dword.substring(0, c);
			}
		}
		if (found == true) {
			ArrayList<String> flags = dr.getFlags();
			if (flags.contains("$atend")) {
				if (currentLine.indexOf(" ") != -1) {
					logger.log(Level.INFO, "Translator-> {0} was not found in dict ($atend)", dword);
					return null;
				}
			}
			if (dr.getPhoneme() != null) {
				if (dr.getPhoneme().startsWith("%")) {
					phoneme += dr.getPhoneme().substring(1);
				} else {
					phoneme += dr.getPhoneme();
				}
			}
			MatchedDictEntry mde = new MatchedDictEntry(phoneme, dr);
			logger.log(Level.INFO, "searchDict found {0} as {1}", new Object[]{dr.getWord(), dr.getPhoneme()});
			return mde;
		}
		else {
			logger.log(Level.INFO, "Translator-> {0} was not found in dict", dword);
			return null;
		}
	}

	private static String replaceCharAt(String s, int pos, char c) {
		StringBuffer buf = new StringBuffer( s );
		buf.setCharAt( pos, c );
		return buf.toString( );
	}

	private static String removeCharAt(String s, int pos) {
		StringBuffer buf = new StringBuffer( s.length() - 1 );
		buf.append( s.substring(0,pos) ).append( s.substring(pos+1) );
		return buf.toString();
	}

/*
 * 			if (line.charAt(pos) == '.' || line.charAt(pos) == '?' || line.charAt(pos) == '!') {
				Character ch;
				try {
					ch = line.charAt(pos+1);
					line = replaceCharAt(line, pos, ' ');
				}
				catch (IndexOutOfBoundsException e) {
					line = removeCharAt(line, pos);
				}
				System.out.println(line);
			}

 */
	public String textToPhoneme(String line) throws Exception {
		logger.log(Level.INFO, "Translator-> called for:{0}", line);
		String phoneme = "";
		int end = line.length(), pos = 0;
		boolean newWord = true;
		while (pos < end) {
			String phon = "";
			if (line.charAt(pos) == ' ') {
				++pos;
				phoneme += " ";
				newWord = true;
			}
			if (newWord == true) {
				newWord = false;
				MatchedDictEntry mde = searchDict(line, pos);
				if (mde != null) {
					phoneme += mde.getPhoneme();
					pos += mde.getRule().getWord().length();
					continue;
				}
			}
/*
				String currentLine = line.substring(pos);
				StringTokenizer st = new StringTokenizer(currentLine);
				String dword = st.nextToken();
				logger.log(Level.INFO, "Translator-> Searching dict for {0}", dword);
				DictRule dr = dict.find(dword);
				if (dr == null) {
					dr = dict.find(dword.toLowerCase());
				}
				if (dr != null) {
					if (dr.getPhoneme() != null) {
						if (dr.getPhoneme().startsWith("%")) {
							phoneme += dr.getPhoneme().substring(1);
						} else {
							phoneme += dr.getPhoneme();
						}
					}
					ArrayList<String> flags = dr.getFlags();
					pos += dword.length();
					continue;
				} else {
					logger.log(Level.INFO, "Translator-> no dict entry, length is {0}", end);
				}
			}
*/
			logger.log(Level.INFO, "Translator-> Iterating for:{0}", line.substring(pos));

			String groupName;
			int score = 0, charsMatched = 0;
			// first search LL group
			if (line.substring(pos).length() >= 2) {
				groupName = line.substring(pos, pos + 2).toLowerCase();
				LLGroup llg = groups2.get(groupName);
				logger.log(Level.INFO, "Translator-> Searching for group {0}", groupName);
				if (llg != null) {
					logger.log(Level.INFO, "Checking group {0}", llg);
					MatchedRule r = llg.match(line, pos);
					if (r != null) {
						score = r.getScore();
						charsMatched = r.getMatchedChars();
						String ph = r.getRule().getPhoneme();
						if (ph != null) {
							phon = ph;
						}
					}
				}
			}
			// then search L group
			groupName = line.substring(pos, pos + 1).toLowerCase();
			LGroup lg = groups1.get(groupName);
			logger.log(Level.INFO, "Translator-> Searching for group {0}", groupName);
			if (lg != null) {
				logger.log(Level.INFO, "Checking group {0}", lg);
				MatchedRule r = lg.match(line, pos);
				if (r != null) {
					if (score < r.getScore()) {
						score = r.getScore();
						charsMatched = r.getMatchedChars();
						String ph = r.getRule().getPhoneme();
						if (ph != null) {
							phon = ph;
						}
					}
				}
			}
			// then search NL group
			logger.log(Level.INFO, "Translator-> Searching for NL group");
			MatchedRule r = group.match(line, pos);
			if (r != null) {
				if (score < r.getScore()) {
					score = r.getScore();
					charsMatched = r.getMatchedChars();
					String ph = r.getRule().getPhoneme();
					if (ph != null) {
						phon = ph;
					}
				}
			}
			if (charsMatched > 0) {
				logger.log(Level.INFO, "Translator-> advancing {0}", charsMatched);
				pos += charsMatched;
				phoneme += phon;
			} //Added a safety check, because the word hello was getting an infinite loop.
			else {
				if (safety != pos) {
					logger.log(Level.WARNING, "Something weird is going on {0}", pos);
					safety = pos;
				} else {
					throw new Exception("Going into infinite loop. Cause is word: " + line.substring(pos));
				}

			}

		}
		return phoneme;
	}
}
